package us.kg.kotoriforest.senior.dao;

import us.kg.kotoriforest.senior.util.JDBCUtilVer2;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * ClassName: us.kg.kotoriforest.senior.dao.BaseDAO </br>
 * Description: 将操作表的一些重复代码进行封装
 *
 * @author Kotori rotroutine@163.com
 * @since 2024/11/29
 */
public abstract class BaseDAO {
    /**
     * 对表执行增删改操作
     *
     * @param SQL          增删改操作对应的 SQL 语句
     * @param placeholders SQL 语句中占位符的对应赋值 如果没有可以不写 但不能写 null
     * @return 受影响的行数
     */
    protected int executeUpdate(String SQL, Object... placeholders) throws SQLException {
        // 加载驱动 获取连接
        Connection connection = JDBCUtilVer2.getConnection();

        // 获取预编译的 SQL 语句
        try (PreparedStatement preparedStatement = connection.prepareStatement(SQL)) {
            // 替换占位符
            if (placeholders != null) {
                for (int i = 0; i < placeholders.length; i++) {
                    preparedStatement.setObject(i + 1, placeholders[i]);
                }
            }

            // 执行 SQL 语句并且返回结果
            return preparedStatement.executeUpdate();

        } finally {
            // 回收连接 只有当没有开启事务时才直接归还连接
            // 否则以 commit 和 rollback 为准
            if (connection.getAutoCommit()) {
                JDBCUtilVer2.close();
            }
        }
    }

    /**
     * 通用查询 执行结果为 单行单列、单行多列、多行单列、多行多列 的查询
     *
     * @param clazz        ORM 的类对象
     * @param SQL          查询语句
     * @param placeholders 查询语句中占位符的具体赋值
     * @param <T>          ORM 对象的类型
     * @return ORM 对象构成的 List 容器
     */
    protected <T> List<T> executeQuery(Class<T> clazz, String SQL, Object... placeholders) throws SQLException {
        // 加载驱动 获取连接
        Connection connection = JDBCUtilVer2.getConnection();

        // 获取预处理语句
        try (PreparedStatement preparedStatement = connection.prepareStatement(SQL)) {
            // 替换占位符
            if (placeholders != null) {
                for (int i = 0; i < placeholders.length; i++) {
                    preparedStatement.setObject(i + 1, placeholders[i]);
                }
            }

            // 执行语句 得到结果集
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                // 获得结果集的元数据 如列的数量和列名
                ResultSetMetaData metaData = resultSet.getMetaData();
                int columnCount = metaData.getColumnCount(); // 列的数量

                // 根据反射 获取默认构造器
                Constructor<T> constructor = clazz.getDeclaredConstructor();
                constructor.setAccessible(true);

                List<T> results = new ArrayList<>();

                while (resultSet.next()) {
                    T t = constructor.newInstance();

                    for (int i = 1; i <= columnCount; ++i) {
                        String columnName = metaData.getColumnLabel(i); // 结果集中的字段名（列名） 需要我们在 SQL 语句中转成驼峰命名
                        Object value = resultSet.getObject(i); // 字段的值

                        // 通过反射为 ORM 对应的字段赋值
                        Field field = clazz.getDeclaredField(columnName);
                        field.setAccessible(true);
                        field.set(t, value);
                    }

                    results.add(t);
                }

                return results;

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } finally {
            // 回收连接
            if (connection.getAutoCommit()) {
                JDBCUtilVer2.close();
            }
        }
    }

    /**
     * 通用查询 获得查询结果中的第一个值
     *
     * @param clazz        ORM 的类对象
     * @param SQL          查询语句
     * @param placeholders 查询语句中占位符的具体赋值
     * @param <T>          ORM 对象的类型
     * @return 查询结果中的第一个值 如果没有值会返回 null
     */
    protected <T> T executeQueryGetFirst(Class<T> clazz, String SQL, Object... placeholders) throws SQLException {
        List<T> list = executeQuery(clazz, SQL, placeholders);
        if (null == list || list.isEmpty()) {
            return null;
        }

        return list.getFirst();
    }
}
